package io.github.soiff.mdoc.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import io.github.soiff.mdoc.pom.Pom;
import io.github.soiff.mdoc.pom.PomParser;
import io.github.soiff.mdoc.settings.PropertyKeys;
import io.github.soiff.mdoc.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.processing.Filer;
import javax.annotation.processing.FilerException;
import javax.annotation.processing.ProcessingEnvironment;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by zhangh on 2016/6/2.
 *
 * @author : zhangh
 * @version : 1.0.0
 */
@SuppressWarnings({"WeakerAccess", "ResultOfMethodCallIgnored"})
public class Parser {
    private static final Logger log = LoggerFactory.getLogger(Parser.class);
    private static final Joiner JOINER_PATH = Joiner.on(File.separatorChar).skipNulls();

    private final Properties PROPERTIES = System.getProperties();
    private final Map<String, String> ENVS = System.getenv();
    private final Map<String, Writer> WRITERS = new ConcurrentHashMap<>(32);

    public final ObjectMapper om = new ObjectMapper();
    public final ProcessingEnvironment pe;
    public Pom module;

    public final Settings settings = build(getSettings());

    public Parser(ProcessingEnvironment pe) {
        this.pe = pe;
        try {
            module = PomParser.parse("pom.xml", Files.newInputStream(Paths.get("pom.xml")));
        } catch (IOException e) {
            e.printStackTrace();
            module = new Pom.Internal();
        }
    }

    public boolean contains(String key) {
        return PROPERTIES.containsKey(key) || ENVS.containsKey(key);
    }

    public String get(String key) {
        return contains(key) ? PROPERTIES.containsKey(key) ? (String) PROPERTIES.get(key) : ENVS.get(key) : null;
    }

    private Settings build (Settings origin) {
        // Make sure the output directory is exist.
        File parent;
        if (null != origin.getDirectory()) {
            parent = new File(origin.getDirectory());
            if (! parent.exists())
                parent.mkdirs();
            else if (! parent.isDirectory()) {
                parent.delete();
                parent.mkdirs();
            }
        }

        if (null == origin.getRoot())
            origin.setRoot(JOINER_PATH.join(PROPERTIES.get("user.home"), "root"));

        if (null == origin.getAuthor())
            origin.setAuthor(PROPERTIES.getProperty("user.name"));

        Set<String> invalid = new HashSet<>();

        origin.getAnnotations().entrySet().stream()
            .filter(at -> (null == at.getValue() || at.getValue().length() <= 0))
            .forEach(at -> invalid.add(at.getKey()));
        if (invalid.size() > 0)
            log.warn("Annotation will be removed for not specifying any template: {}.", invalid);
        invalid.forEach(i -> origin.getAnnotations().remove(i));

        return origin;
    }

    private Settings getSettings() {
        final String config = get(PropertyKeys.CONFIGURATION);
        if (null == config || config.length() <= 0)
            return Settings.DEFAULT;
        final File configFile = new File(config);
        Settings settings;
        if (configFile.exists()) {
            try {
                settings = om.readValue(configFile, Settings.class);
            } catch (IOException e) {
                log.warn("Parsing configuration file '{}' failed for '{}' , fallback to default: {}",
                    config, e.getClass(), Settings.DEFAULT);
                return Settings.DEFAULT;
            }
        } else {
            try {
                settings = om.readValue(config, Settings.class);
            } catch (IOException e) {
                log.warn("Parsing configuration failed for '{}', fallback to default: {}",
                    e.getCause(), Settings.DEFAULT);
                return Settings.DEFAULT;
            }
        }
        settings.setConfiguration(config);
        return settings;
    }

    public Writer getWriter (String name) {
        final String file = name + "." + settings.getExtension();
        try {
            Writer writer;
            if (null != settings.getDirectory()) {
                writer = new FileWriter(new File(settings.getDirectory(), file), false);
            } else {
                final Filer filer = this.pe.getFiler();
                final FileObject old = filer.getResource(StandardLocation.CLASS_OUTPUT, "", file);
                old.delete();
                writer = new OutputStreamWriter(filer.createResource(StandardLocation.CLASS_OUTPUT, "", file)
                    .openOutputStream());
            }
            WRITERS.put(name, writer);
            return writer;
        } catch (FilerException e) {
            log.warn("File {} already been rendered, will skip.", name);
            return null;
        } catch (IOException e) {
            log.error("Fail to get resource for {}.", e.getCause() == null ? e.getMessage() : e.getCause().toString());
            return null;
        }
    }
}
